﻿using Abp.Domain.Repositories;
using Abp.Domain.Services;
using Abp.Extensions;
using Microsoft.EntityFrameworkCore;
using Mt.Site.Core.Base.Tree;
using Mt.Site.Core.Navigations;
using Mt.Site.Core.Navigations.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Mt.Site.Core.Synchronization.Navigations
{
    public class MenuStore : DomainService, IMenuStore
    {
        private readonly IRepository<Menu, Guid> _menuRepository;
        private readonly IRepository<Bundling, Guid> _bundlingRepository;
        private readonly IMenuManager _menuManager;

        public MenuStore(
            IRepository<Menu, Guid> menuRepository,
            IRepository<Bundling, Guid> bundlingRepository,
            IMenuManager menuManager)
        {
            _menuRepository = menuRepository;
            _bundlingRepository = bundlingRepository;
            _menuManager = menuManager;
        }

        public async Task DeleteMenuItemAsync(Menu toEntity, string languageName)
        {
            if (!toEntity.IsDeleted)
            {
                var children = await FindChildren(toEntity.Code, toEntity.TenantId);

                foreach (var child in children)
                {
                    await _menuManager.DeleteItemAsync(child.Id, toEntity.TenantId, languageName);
                }

                await _menuManager.DeleteItemAsync(toEntity.Id, toEntity.TenantId, languageName);
            }
        }

        public async Task<Guid> CreateMenuSync(Menu fromEntity, int toTenantId)
        {
            var data = await CreateMenuSync2(fromEntity, toTenantId);
            return data.MenuId.Value;
        }

        public async Task<Guid> UpdateMenuSync(Menu toEntity, Menu fromEntity)
        {
            /* 
             * 主动同步：
             * 1. 找到了目标数据，
             *    先查找fromEntity的上级栏目数据，是否是toEntity上级栏目数据的源数据（确认是否有栏目移动操作）。
             *      1.1 保障toEntity的上级栏目数据（子站可以自己删除数据，目标栏目及其上级栏目可能都处于被删除状态）
                    * 1. 若存在并未删除则继续同步目标数据
                    * 2. 若已删除，则依序把它们恢复过来。
                    * 3. 若上级栏目数据丢失（几乎不可能），查找母站中对应的上级栏目，将其移至对应的栏目下（如果终止复制，会导致这条数据无法恢复）
             *      1.2 如果有栏目移动操作，则需要修改toEntity及其下级所有栏目的Code. 并修改toEntity.ParentId，toEntity.Code
            */

            // 确认是否需要改变toEntity.ParentId
            var toEntity_Parent = await IsNeedChangeParent(toEntity, fromEntity);
            if (toEntity_Parent.MenuId != Guid.Empty)
            {
                if (toEntity_Parent.NeedChangeParent || (toEntity.IsDeleted && !fromEntity.IsDeleted))
                {
                    //可能在被禁用了自动同步(或被删除)的这段时间里，上级的结构有变化过
                    string oldCode = toEntity.Code;
                    if (toEntity_Parent.IsCreatingOrUndeletingParent)
                    {
                        toEntity.Code = CodeHelper.AppendCode(toEntity_Parent.Code, CodeHelper.CreateCode(1));
                    }
                    else
                    {
                        toEntity.Code = await GetNextChildCodeAsync(toEntity_Parent.MenuId, toEntity.TenantId, toEntity_Parent.Code);
                    }
                    toEntity.Order = fromEntity.Order; //int.Parse(Menu.GetLastUnitCode(toEntity.Code));
                    toEntity.ParentId = toEntity_Parent.MenuId;

                    //改变toEntity的下级栏目的Code
                    if (!toEntity.IsDeleted/*通过Code查找下级，而已删除数据的Code值是不可信的*/)
                    {
                        var children = await FindChildren(oldCode, toEntity.TenantId);
                        foreach (var child in children)
                        {
                            child.Code = CodeHelper.AppendCode(toEntity.Code, CodeHelper.GetRelativeCode(child.Code, oldCode));
                        }
                    }
                }

                MapToACopy(fromEntity, toEntity);
                _menuRepository.Update(toEntity);
                return toEntity.Id;
            }

            return Guid.Empty;
        }
        public async Task ResetMenuCacheAsync(int tenantId)
        {
            await _menuManager.ResetMenuCacheAsync(tenantId);
        }

        #region Private

        private async Task<TempMenuInfo> GetParentIdForToEntity(Guid? fromEntity_ParentId, int toTenantId)
        {
            Guid? toEntity_ParentId = null;
            bool isCreatingOrUndeletingParent = false;
            if (fromEntity_ParentId != null)
            {
                var fromEntity_Parent = _menuRepository.FirstOrDefault(fromEntity_ParentId.Value);
                if (fromEntity_Parent != null)
                {
                    var toEntity_Parent = _menuRepository.FirstOrDefault(
                            item => item.AuxiliaryId == fromEntity_Parent.AuxiliaryId && item.TenantId == toTenantId);

                    if (toEntity_Parent != null)
                    {
                        //上级栏目数据存在，
                        var parentlist = await UnDeletedMenu(toEntity_Parent);
                        if (parentlist.NotWrongData)
                        {
                            if (parentlist.ParentsWereDeleted)
                            {
                                isCreatingOrUndeletingParent = true;

                                return new TempMenuInfo
                                {
                                    MenuId = toEntity_Parent.Id,
                                    Code = parentlist.Menus[0].Code
                                };
                            }
                            toEntity_ParentId = toEntity_Parent.Id;
                        }
                        else
                        {
                            Logger.Warn($"栏目同步出错：栏目{string.Join("、", parentlist.Menus.Select(u=>u.Id).ToList())}的上级数据不存在！");
                            //toEntity_Parent的上级数据丢失（是脏数据）
                            toEntity_ParentId = Guid.Empty;
                        }
                    }
                    else
                    {
                        //当前查找到的父级栏目为新创建，尚未保存到数据库，也就无法用常规方法生成Code
                        isCreatingOrUndeletingParent = true;
                        //上级也不存在时，先复制上级栏目（因为新增/修改的栏目需作为它的子栏目）
                        return await CreateMenuSync2(fromEntity_Parent, toTenantId);
                    }
                }
                else
                {
                    //母站中数据（上级栏目）已不存在(数据有误)，导致复制中断。
                    toEntity_ParentId = Guid.Empty;
                }
            }

            return new TempMenuInfo
            {
                MenuId = toEntity_ParentId ,
                IsCreatingOrUndeletingParent = isCreatingOrUndeletingParent
            };
        }

        private async Task<TempMenuInfo> CreateMenuSync2(Menu fromEntity, int toTenantId)
        {
            /* 
             *  子站中的目标数据不存在：
             *      查找其上级栏目数据，
                    * 1. 若存在并未删除则复制目标数据(复制排序号)到其下
                    * 2. 若已删除，则依序把它们恢复过来，再复制目标数据到其下。
                    * 3. 若上级栏目数据不存在，则先递归复制上级栏目
            */
            var toEntity_Parent = await GetParentIdForToEntity(fromEntity.ParentId, toTenantId);

            if (toEntity_Parent.MenuId != Guid.Empty)
            {
                var toEntity = GetANewCopy(fromEntity, toEntity_Parent.MenuId, toTenantId);
                if (toEntity_Parent.IsCreatingOrUndeletingParent)
                {
                    //父级栏目是新创建的，因尚未同步到数据库，因而不能通过常规方法创建子栏目的Code
                    //但因为是递归执行，这个父级栏目的Code已经获取到了。
                    toEntity.Code = CodeHelper.AppendCode(toEntity_Parent.Code, CodeHelper.CreateCode(1));
                }
                else
                {
                    toEntity.Code = await GetNextChildCodeAsync(toEntity_Parent.MenuId, toTenantId, toEntity_Parent.Code);
                }

                _menuRepository.Insert(toEntity);
                return new TempMenuInfo
                {
                    MenuId = toEntity.Id,
                    Code = toEntity.Code
                };
            }

            return new TempMenuInfo
            {
                MenuId = Guid.Empty
            };
        }

        private async Task<TempMenuInfo> IsNeedChangeParent(Menu toEntity, Menu fromEntity)
        {
            var toEntity_Parent_Old = toEntity.ParentId;
            var toEntity_Parent = await GetParentIdForToEntity(fromEntity.ParentId, toEntity.TenantId);

            if (toEntity_Parent.MenuId != toEntity_Parent_Old)
            {
                toEntity_Parent.NeedChangeParent = true;
            }

            return toEntity_Parent;
        }

        private Menu GetANewCopy(Menu fromEntry, Guid? parentId, int toTenantId)
        {
            var newEntry = new Menu();
            var id = Guid.NewGuid();
            newEntry.Id = id;
            newEntry.AuxiliaryId = fromEntry.AuxiliaryId;

            newEntry.BackEndModuleId = fromEntry.BackEndModuleId;
            newEntry.FrontEndPageId = fromEntry.FrontEndPageId;
            newEntry.DeviceType = fromEntry.DeviceType;
            newEntry.DisplayName = fromEntry.DisplayName;
            newEntry.DisplayType = fromEntry.DisplayType;
            newEntry.BackendMenuType = fromEntry.BackendMenuType;
            newEntry.BackendIcon = fromEntry.BackendIcon;

            newEntry.IsNavigation4FrontEnd = fromEntry.IsNavigation4FrontEnd;
            newEntry.IsSpecial = fromEntry.IsSpecial;
            newEntry.AutoSynchronous = true;
            newEntry.AutoSynchronousForContent = true;
            newEntry.ChannelRecommendation = fromEntry.ChannelRecommendation;
            newEntry.HomeRecommendation = fromEntry.HomeRecommendation;
            newEntry.IsActive = fromEntry.IsActive;
            newEntry.MenuItemGroups = fromEntry.MenuItemGroups;
            newEntry.CustomUrl = fromEntry.CustomUrl;
            newEntry.CustomData = fromEntry.CustomData;

            newEntry.SourceID = fromEntry.Id;
            newEntry.TenantId = toTenantId;

            newEntry.ParentId = parentId;

            newEntry.Order = fromEntry.Order;

            return newEntry;
        }

        private void MapToACopy(Menu fromEntry, Menu toEntity)
        {
            toEntity.AuxiliaryId = fromEntry.AuxiliaryId;
            toEntity.BackEndModuleId = fromEntry.BackEndModuleId;
            toEntity.FrontEndPageId = fromEntry.FrontEndPageId;
            toEntity.DeviceType = fromEntry.DeviceType;
            toEntity.DisplayName = fromEntry.DisplayName;
            toEntity.DisplayType = fromEntry.DisplayType;
            toEntity.BackendMenuType = fromEntry.BackendMenuType;
            toEntity.BackendIcon = fromEntry.BackendIcon;

            toEntity.IsNavigation4FrontEnd = fromEntry.IsNavigation4FrontEnd;
            toEntity.IsSpecial = fromEntry.IsSpecial;

            toEntity.ChannelRecommendation = fromEntry.ChannelRecommendation;
            toEntity.HomeRecommendation = fromEntry.HomeRecommendation;
            toEntity.IsActive = fromEntry.IsActive;
            toEntity.MenuItemGroups = fromEntry.MenuItemGroups;
            toEntity.CustomUrl = fromEntry.CustomUrl;
            toEntity.CustomData = fromEntry.CustomData;

            toEntity.SourceID = fromEntry.Id;
            // 为避免打乱子站的栏目顺序，在修改栏目时，不同步排序号。
            //toEntity.Order = fromEntry.Order;
            toEntity.IsDeleted = fromEntry.IsDeleted;
        }

        private async Task<string> GetNextChildCodeAsync(Guid? parentId, int tenantId)
        {
            //虽然code不再决定栏目排序，但如果有序一些依然会好看很多，且必须保障同租户下Code的唯一性。
            var lastChild = await _menuRepository.Query(
                q => q.Where(u => u.ParentId == parentId && u.TenantId == tenantId && u.IsDeleted == false)
                .OrderByDescending(u => u.Code))
                .FirstOrDefaultAsync();

            if (lastChild == null)
            {
                string parentCode = null;
                if (parentId != null)
                {
                    parentCode = (await _menuRepository.GetAsync(parentId.Value)).Code;
                }
                return CodeHelper.AppendCode(parentCode, CodeHelper.CreateCode(1));
            }

            return CodeHelper.CalculateNextCode(lastChild.Code);
        }
        private async Task<string> GetNextChildCodeAsync(Guid? parentId, int tenantId, string parentCode)
        {
            //虽然code不再决定栏目排序，但如果有序一些依然会好看很多，且必须保障同租户下Code的唯一性。
            var lastChild = await _menuRepository.Query(
                q => q.Where(u => u.ParentId == parentId && u.TenantId == tenantId && u.IsDeleted == false)
                .OrderByDescending(u => u.Code))
                .FirstOrDefaultAsync();

            if (lastChild == null)
            {
                return CodeHelper.AppendCode(parentCode, CodeHelper.CreateCode(1));
            }

            return CodeHelper.CalculateNextCode(lastChild.Code);
        }

        private async Task<UnDeletedMenuListItem> UnDeletedMenu(Menu entity)
        {
            var list = new List<Menu>();
            list.Add(entity);

            var notWrongData = await UnDeletedMenu(list);
            var parentsWereDeleted = false;
            if (notWrongData)
            {
                for (int i = list.Count - 1; i >= 0; i--)
                {
                    var item = list[i];
                    if (item.IsDeleted)
                    {
                        //被删除的数据Code可能是有误的，
                        //在恢复的时候，也需要更新它的Code ,
                        //而这种情况下它的下级也必然是被删除状态，在被恢复时也会重置Code
                        entity.IsDeleted = false;
                        parentsWereDeleted = true;
                        var parentItem = (i == list.Count - 1 ? null : list[i + 1]);
                        if(parentItem == null)
                        {
                            entity.Code = await GetNextChildCodeAsync(null, entity.TenantId);
                        }
                        else
                        {
                            if (parentItem.IsDeleted)
                            {
                                entity.Code = CodeHelper.AppendCode(parentItem.Code, CodeHelper.CreateCode(1));
                            }
                            else
                            {
                                entity.Code = await GetNextChildCodeAsync(parentItem.Id, entity.TenantId);
                            }
                        }
                    }
                }
            }

            return new UnDeletedMenuListItem
            {
                Menus = list,
                NotWrongData = notWrongData,
                ParentsWereDeleted = parentsWereDeleted
            };
        }
        private async Task<bool> UnDeletedMenu(List<Menu> list)
        {
            var topOne = list[list.Count - 1];
            if (topOne.ParentId != null)
            {
                var parent = await _menuRepository.FirstOrDefaultAsync(topOne.ParentId.Value);
                if(parent == null)
                {
                    return false;
                }
                list.Add(parent);
                return await UnDeletedMenu(list);
            }
            else
            {
                return true;
            }
        }

        private async Task<List<Menu>> FindChildren(string code, int tenantId)
        {
            //因为Code可以重置，所以用Code查找栏目时，如果不过滤已删除的栏目的话，会命中很多脏数据。
            if(code.IsNullOrEmpty() || code == "0")
            {
                return new List<Menu>();
            }

            var code_ = $"{code}.";
            return await _menuRepository.GetAllListAsync(
                u => u.Code.StartsWith(code_) && u.TenantId == tenantId && u.IsDeleted == false
            );
        }

        #endregion

        #region StylesAndScripts

        public async Task StylesAndScriptsSync(Guid fromMenuId, Guid toMenuId, int toTenantId)
        {
            var fromEntity = await _bundlingRepository.FirstOrDefaultAsync(c => c.MenuId == fromMenuId);
            if (fromEntity == null)
            {
                await _bundlingRepository.DeleteAsync(c => c.MenuId == toMenuId);
                return;
            }

            var b = await _bundlingRepository.FirstOrDefaultAsync(c => c.MenuId == toMenuId);
            if (b == null)
            {
                b = new Bundling();
                b.TenantId = toTenantId;
                b.MenuFid = fromEntity.MenuFid;
                b.MenuId = toMenuId;
                b.AutoSynchronous = true;
                b.StylePc = fromEntity.StylePc;
                b.StyleMobile = fromEntity.StyleMobile;
                b.StyleRltPc = fromEntity.StyleRltPc;
                b.StyleRltMobile = fromEntity.StyleRltMobile;
                b.ScriptPc = fromEntity.ScriptPc;
                b.ScriptMobile = fromEntity.ScriptMobile;
                b.ScriptPcBottom = fromEntity.ScriptPcBottom;
                b.ScriptMobileBottom = fromEntity.ScriptMobileBottom;
                b.IsDeleted = fromEntity.IsDeleted;
                b.Id = Guid.NewGuid();
                await _bundlingRepository.InsertAsync(b);
            }
            else
            {
                b.StylePc = fromEntity.StylePc;
                b.StyleMobile = fromEntity.StyleMobile;
                b.StyleRltPc = fromEntity.StyleRltPc;
                b.StyleRltMobile = fromEntity.StyleRltMobile;
                b.ScriptPc = fromEntity.ScriptPc;
                b.ScriptMobile = fromEntity.ScriptMobile;
                b.ScriptPcBottom = fromEntity.ScriptPcBottom;
                b.ScriptMobileBottom = fromEntity.ScriptMobileBottom;
                b.IsDeleted = fromEntity.IsDeleted;
                await _bundlingRepository.UpdateAsync(b);
            }
        }

        public async Task<Guid> StylesAndScriptsAutoSync(Bundling toEntity, Bundling fromEntity, Guid toMenuId, int toTenantId)
        {
            if (fromEntity == null)
            {
                if (toEntity != null)
                {
                    await _bundlingRepository.DeleteAsync(c => c.MenuId == toMenuId);
                }
                return Guid.Empty;
            }

            if (toEntity == null)
            {
                toEntity = new Bundling();
                toEntity.TenantId = toTenantId;
                toEntity.MenuFid = fromEntity.MenuFid;
                toEntity.MenuId = toMenuId;
                toEntity.AutoSynchronous = true;
                toEntity.StylePc = fromEntity.StylePc;
                toEntity.StyleMobile = fromEntity.StyleMobile;
                toEntity.StyleRltPc = fromEntity.StyleRltPc;
                toEntity.StyleRltMobile = fromEntity.StyleRltMobile;
                toEntity.ScriptPc = fromEntity.ScriptPc;
                toEntity.ScriptMobile = fromEntity.ScriptMobile;
                toEntity.ScriptPcBottom = fromEntity.ScriptPcBottom;
                toEntity.ScriptMobileBottom = fromEntity.ScriptMobileBottom;
                toEntity.IsDeleted = fromEntity.IsDeleted;
                toEntity.Id = Guid.NewGuid();
                await _bundlingRepository.InsertAsync(toEntity);

                return toEntity.Id;
            }
            else
            {
                toEntity.StylePc = fromEntity.StylePc;
                toEntity.StyleMobile = fromEntity.StyleMobile;
                toEntity.StyleRltPc = fromEntity.StyleRltPc;
                toEntity.StyleRltMobile = fromEntity.StyleRltMobile;
                toEntity.ScriptPc = fromEntity.ScriptPc;
                toEntity.ScriptMobile = fromEntity.ScriptMobile;
                toEntity.ScriptPcBottom = fromEntity.ScriptPcBottom;
                toEntity.ScriptMobileBottom = fromEntity.ScriptMobileBottom;
                toEntity.IsDeleted = fromEntity.IsDeleted;
                await _bundlingRepository.UpdateAsync(toEntity);

                return toEntity.Id;
            }
        }

        #endregion

        private class UnDeletedMenuListItem
        {
            public List<Menu> Menus { get; set; }

            public bool NotWrongData { get; set; }

            public bool ParentsWereDeleted { get; set; }
        }

        private class TempMenuInfo
        {
            public Guid? MenuId { get; set; }
            public string Code { get; set; }

            public bool NeedChangeParent { get; set; }

            public bool IsCreatingOrUndeletingParent { get; set; }
        }
    }
}
